/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */
package com.pig4cloud.pigx.wallet.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.crypto.symmetric.AES;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.utils.id.IdWorker;
import com.pig4cloud.pigx.wallet.configuration.SendServiceCharge;
import com.pig4cloud.pigx.wallet.constant.WalletConstant;
import com.pig4cloud.pigx.wallet.constant.WalletStatusEnum;
import com.pig4cloud.pigx.wallet.entity.*;
import com.pig4cloud.pigx.wallet.enums.TimingImplementRecordEnums;
import com.pig4cloud.pigx.wallet.mapper.ImputationRecordMapper;
import com.pig4cloud.pigx.wallet.service.AccountService;
import com.pig4cloud.pigx.wallet.service.CurrencyService;
import com.pig4cloud.pigx.wallet.service.ImputationRecordService;
import lombok.AllArgsConstructor;
import okhttp3.OkHttpClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.tron.trident.core.ApiWrapper;
import org.tron.trident.core.contract.Trc20Contract;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthGasPrice;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.tx.Contract;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
 * 归集列表
 *
 * @author
 * @date 2022-08-16 15:27:24
 */
@Service
@AllArgsConstructor
public class ImputationRecordServiceImpl extends ServiceImpl<ImputationRecordMapper, ImputationRecord> implements ImputationRecordService {

	private final AccountService accountService;

	private final Web3j web3j;

	private final AES aes;

	private final RabbitTemplate rabbitTemplate;

	private final CurrencyService currencyService;

	private final OkHttpClient okHttpClient;

	private final SendServiceCharge charge;

	@Override
	public void create(ImputationRecord imputationRecord) {
		// 定时任务记录
		TimingImplementRecord record = new TimingImplementRecord();
		record.setId(new IdWorker().nextId());
		record.setStatus(TimingImplementRecordEnums.Status.IMPUTATION.getValue());
		record.setType(TimingImplementRecordEnums.Type.IMPUTATION.getValue());
		record.setArrivalAddress(imputationRecord.getArrivalAddress());
		record.setQuantity(BigDecimal.ZERO);
		LocalDateTime now = LocalDateTime.now();
		record.setCreateTime(now);
		record.insert();
		try {
			// 筛选账户主网
			List<Account> accountList = accountService.list(new LambdaQueryWrapper<Account>()
					.eq(Account::getMainNetwork, imputationRecord.getMainNetwork())
			);

			long snowflakeNextId = IdUtil.getSnowflakeNextId();

			if (WalletConstant.ETH.equals(imputationRecord.getMainNetwork())) {
				// ETH 以太坊主网
				Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
						// 公链
						.eq(Currency::getPublicChain, WalletConstant.ETH)
						// 状态
						.eq(Currency::getState, WalletConstant.TRUE)
						// 币种名称
						.eq(Currency::getName, WalletConstant.USDT)
				);

				// 出口参数
				List<TypeReference<?>> outputParameters = new ArrayList<>();
				TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {
				};
				outputParameters.add(typeReference);
				String methodName = "balanceOf";

				accountList.forEach(account -> {
					// 入口参数
					List<Type> inputParameters = new ArrayList<>();

					// 用户账户地址
					Address userAddress = new Address(account.getAddress());
					inputParameters.add(userAddress);

					// 获取ETH余额
					Function function = new Function(methodName, inputParameters, outputParameters);
					String data = FunctionEncoder.encode(function);
					Request<?, EthCall> ethCallRequest = web3j.ethCall(
							Transaction.createEthCallTransaction(
									// 地址（账户）
									account.getAddress(),
									// 智能合约地址（币种列表）
									currency.getSmartContractAddress(),
									data
							),
							DefaultBlockParameterName.LATEST);
					EthCall send;
					try {
						send = ethCallRequest.send();
					} catch (IOException e) {
						throw new RuntimeException(e);
					}

					String valueStr = new BigInteger(send.getValue().substring(2), 16).toString();
					// 保留币种列表中该币种指定的精度，转化成String类型数据
					String balance = NumberUtil.toBigDecimal(valueStr).movePointLeft(Integer.parseInt(currency.getAccuracy())).stripTrailingZeros().toPlainString();
					BigDecimal bigDecimal = NumberUtil.toBigDecimal(balance);
					// 比较ETH钱包余额和归集的数量
					boolean greaterOrEqual = NumberUtil.isGreaterOrEqual(bigDecimal, imputationRecord.getQuantity());

					if (greaterOrEqual) {
						// 如果手续费不足则补充手续费
						charge.ethFee(new RechargeRecord(){{
							setArrivalAddress(account.getAddress());
						}});
						// ETH钱包余额 >= 归集的数量
						String decryptStr = aes.decryptStr(account.getPrivateKey());
						Credentials credentials = Credentials.create(decryptStr);

						// 查询指定地址发生的交易数量
						EthGetTransactionCount ethGetTransactionCount;
						try {
							ethGetTransactionCount = web3j.ethGetTransactionCount(account.getAddress(), DefaultBlockParameterName.LATEST).sendAsync().get();
						} catch (InterruptedException | ExecutionException e) {
							throw new RuntimeException(e);
						}

						BigInteger nonce = ethGetTransactionCount.getTransactionCount();

						// 查询当前的手续费gas价格，单位：wei。
						EthGasPrice ethGasPrice;
						try {
							ethGasPrice = web3j.ethGasPrice().send();
						} catch (IOException e) {
							throw new RuntimeException(e);
						}
						BigInteger gasPrice = ethGasPrice.getGasPrice();
						// 账户ETH公链余额
						BigInteger bigInteger = NumberUtil.toBigInteger(valueStr);

						// 封装转账交易
						Function transferFunction = new Function(
								"transfer",
								Arrays.asList(new Address(imputationRecord.getArrivalAddress()), new Uint256(bigInteger)),
								Collections.singletonList(new TypeReference<Type>() {
								}));

						String encodedFunction = FunctionEncoder.encode(transferFunction);

						// 一次交易中Gas的可用上限
						BigInteger gasLimit = Contract.GAS_LIMIT;

						// 签名交易
						RawTransaction rawTransaction = RawTransaction.createTransaction(nonce,
								gasPrice,
								BigInteger.valueOf(100_000L),
								currency.getSmartContractAddress(), encodedFunction);

						byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
						String hexValue = Numeric.toHexString(signMessage);

						// 广播交易，向为签名交易创建一个新的消息调用交易或合约。
						EthSendTransaction ethSendTransaction;
						try {
							ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
						} catch (InterruptedException | ExecutionException e) {
							throw new RuntimeException(e);
						}

						// 保存归集列表
						ImputationRecord imputationRecordSave = new ImputationRecord();
						// 账户id
						imputationRecordSave.setAccountId(account.getId());
						// 到账地址
						imputationRecordSave.setArrivalAddress(imputationRecord.getArrivalAddress());
						// 币种（1：USDT）
						imputationRecordSave.setCurrency(imputationRecord.getCurrency());
						// 主网（1：以太坊，2：波场）
						imputationRecordSave.setMainNetwork(imputationRecord.getMainNetwork());
						// 归集id
						imputationRecordSave.setNotionalpoolingId(snowflakeNextId);
						// 数量
						imputationRecordSave.setQuantity(bigDecimal);
						// 来源地址
						imputationRecordSave.setSourceAddress(account.getAddress());
						// 用户ID
						imputationRecordSave.setUserId(account.getUserId());

						if (ethSendTransaction.getError() != null) {
							// 状态（1：归集中，2：成功，3：失败）
							imputationRecordSave.setState(WalletStatusEnum.IMPUTATION_FAILED.getStatus());
							save(imputationRecordSave);
						} else {
							// 状态（1：归集中，2：成功，3：失败）
							imputationRecordSave.setState(WalletStatusEnum.IMPUTATION_COMPLETED.getStatus());
							// 交 易 哈 希
							String transactionHash = ethSendTransaction.getTransactionHash();
							imputationRecordSave.setTransactionHash(transactionHash);
							save(imputationRecordSave);
							record.setQuantity(record.getQuantity().add(bigDecimal));
						}
					}

				});
			} else {
				// TRON 波场主网

				// 币种列表中，TRON信息
				Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
						.eq(Currency::getPublicChain, WalletConstant.TRON)
						.eq(Currency::getState, WalletConstant.TRUE)
						.eq(Currency::getName, WalletConstant.USDT)
				);

				// 归集的账户列表不为空
				if (CollUtil.isNotEmpty(accountList)) {
					for (Account account : accountList) {
						try {
							// 解密私钥
							String decryptStr = aes.decryptStr(account.getPrivateKey());
							// 主网，使用TronGrid
							ApiWrapper wrapper = ApiWrapper.ofMainnet(decryptStr, currency.getApiKey());
							// Nile测试网，使用的节点来自Nile官网
//							ApiWrapper wrapper = ApiWrapper.ofNile(decryptStr);

							org.tron.trident.core.contract.Contract contract = wrapper.getContract(currency.getSmartContractAddress());
							// TRON余额查询
							Trc20Contract trc20Contract = new Trc20Contract(contract, account.getAddress(), wrapper);
							BigInteger bigInteger = trc20Contract.balanceOf(account.getAddress());
							String balance = NumberUtil.toBigDecimal(String.valueOf(bigInteger))
									.movePointLeft(Integer.parseInt(currency.getAccuracy()))
									.stripTrailingZeros()
									.toPlainString();
							BigDecimal bigDecimal = NumberUtil.toBigDecimal(balance);
							boolean greaterOrEqual = NumberUtil.isGreaterOrEqual(bigDecimal, NumberUtil.toBigDecimal(imputationRecord.getQuantity()));

							if (greaterOrEqual) {
								charge.tronFee(new RechargeRecord(){{
									setArrivalAddress(account.getAddress());
								}});
								ImputationRecord imputationRecordSave = new ImputationRecord();
								imputationRecordSave.setAccountId(account.getId());
								imputationRecordSave.setState(WalletStatusEnum.IMPUTATING.getStatus());
								imputationRecordSave.setArrivalAddress(imputationRecord.getArrivalAddress());
								imputationRecordSave.setCurrency(imputationRecord.getCurrency());
								imputationRecordSave.setMainNetwork(imputationRecord.getMainNetwork());
								imputationRecordSave.setNotionalpoolingId(snowflakeNextId);
								imputationRecordSave.setQuantity(bigDecimal);
								imputationRecordSave.setSourceAddress(account.getAddress());

								imputationRecordSave.setUserId(account.getUserId());

								// 转账
								String transfer = null;
								try {
									transfer = trc20Contract.transfer(
											imputationRecord.getArrivalAddress(),
											bigInteger.longValue(),
											0,
											"memo",
											100000000);
								} catch (RuntimeException e) {
									imputationRecordSave.setState(WalletStatusEnum.IMPUTATION_FAILED.getStatus());
									save(imputationRecordSave);
									continue;
								}
								imputationRecordSave.setTransactionHash(transfer);
								save(imputationRecordSave);
								record.setQuantity(record.getQuantity().add(bigDecimal));
								// 验证交易结果
								rabbitTemplate.convertAndSend(
										"wallet.drive.exchange",
										"wallet.drive.imputation.binding",
										imputationRecordSave.getId());
							}
						} catch (Exception ignored) {
						}
					}
				}
			}
		} catch (Exception e) {
			record.setStatus(TimingImplementRecordEnums.Status.CUSTOMER_WALLET.getValue());
		} finally {
			record.updateById();
		}
	}

}
